package org.hackystat.devcathlon.engine.match;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.devcathlon.engine.db.hibernate.HibernateUtil;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventManager;
import org.hackystat.devcathlon.engine.project.TeamProject;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 * A thread-safe singleton class that manages access to the Matches in this system.
 * 
 * @author Philip Johnson
 */
public class MatchManager {
  /** The singleton instance of this class. */
  private static MatchManager theInstance = new MatchManager();

  /** The private constructor. */
  private MatchManager() {
    // does nothing at this point; will initialize from database later.
  }

  /**
   * Returns the singleton instance of the MatchManager.
   * 
   * @return The MatchManager.
   */
  public static MatchManager getInstance() {
    return MatchManager.theInstance;
  }

  /**
   * Adds a match to the system. Performs validity checking, including determining that the name is
   * unique. Should also check that teams2projects and events have legal values. Returns the newly
   * created Match instance.
   * 
   * @param name The name of this match
   * @param startDate The startDate.
   * @param endDate The endDate.
   * @param teams2projects A set of (team, project) tuples.
   * @param events A list of events that the teams will compete in.
   * @return The newly created Match.
   * @throws Exception If there is an error with creating a match
   */
  public synchronized Match addMatch(String name, XMLGregorianCalendar startDate,
      XMLGregorianCalendar endDate, Map<Team, Project> teams2projects, List<Event> events)
      throws Exception {
    Match match = getMatch(name);
    if (null != match) {
      return match;
    }
    // Create the match
    match = new Match(name, startDate, endDate, teams2projects, events);
    // Create the event tasks.
    EventManager.getInstance().createEventTasks(match, events);
    
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      session.save(match);  
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return match;
  }
  
  /**
   * Adds a match to the system. Performs validity checking, including determining that the name is
   * unique. Should also check that teams2projects and events have legal values. Returns the newly
   * created Match instance.
   * 
   * @param name The name of this match
   * @param startDate The startDate.
   * @param endDate The endDate.
   * @param teamProjects A set of (team, team project) tuples.
   * @param events A list of events that the teams will compete in.
   * @return The newly created Match.
   * @throws Exception If there is an error with creating a match
   */
  public synchronized Match addMatch(String name, XMLGregorianCalendar startDate,
      XMLGregorianCalendar endDate, List<Event> events, Map<Team, TeamProject> teamProjects)
      throws Exception {
    Match match = getMatch(name);
    if (null != match) {
      return match;
    }
    // Create the match
    match = new Match();
    match.setName(name);
    match.setStartDateTime(startDate.toString());
    match.setEndDateTime(endDate.toString());
    match.setEvents(events);
    match.setTeamProjects(teamProjects);
    // Create the event tasks.
    EventManager.getInstance().createEventTasks(match, events);
    
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      session.save(match);  
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return match;
  }

  /**
   * Reconfigures the events for an existing match.
   * 
   * @param matchName The match name.
   * @param newEvents The list of new events.
   * @return The reconfigured match.
   */
  public synchronized Match reconfigEvents(String matchName, List<Event> newEvents) {
    Match match = getMatch(matchName);
    
    if (null == match) {
      return match;
    }
    List<Event> cancelEventTasks = new ArrayList<Event>(match.getEvents());
    if (cancelEventTasks.contains(newEvents.get(0))) {
      System.out.print("equal");
    }
    cancelEventTasks.removeAll(newEvents);
    for (Event event : cancelEventTasks) {
      match.getTimer(event).cancel();
      match.removeTimer(event);
    }

    List<Event> startEventTasks = new ArrayList<Event>(newEvents);
    startEventTasks.removeAll(match.getEvents());
    EventManager.getInstance().createEventTasks(match, startEventTasks);

    match.setEvents(newEvents);
    return match;
  }

  /**
   * Returns the match associated with the name, or null if not found.
   * 
   * @param name The name of the match to be retrieved.
   * @return The match instance, or null if not found.
   */
  public synchronized Match getMatch(String name) {
    Match match =  null;
    String hqlSelect = "from Match m where m.name = :name";
    
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      Query query = session.createQuery(hqlSelect).setString("name", name);
      match = (Match) query.uniqueResult();
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return match;
  }
  
  /**
   * Updates a given match instance.
   * 
   * @param match The match to update.
   * @return The match.
   */
  public synchronized Match updateMatch(Match match) {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      session.update(match);
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return match;
  }

  /**
   * Ensures that the passed match name is no longer in the set of defined Matches. Does nothing if
   * the match name is not known.
   * 
   * @param name The match name.
   */
  public synchronized void deleteMatch(String name) {
    Match match = getMatch(name);
    if (null == match) {
      return;
    }
    
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      session.delete(match);
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }

  /**
   * Returns a collection of all matches.
   * 
   * @return The collection of matches.
   */
  @SuppressWarnings("unchecked")
  public synchronized Collection<Match> getAllMatches() {
    List<Match> matches = null;
    String hqlSelect = "from Match";
    
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      matches = (List<Match>) session.createQuery(hqlSelect).list();
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return matches;
  }

  /**
   * Get unique match name ids.
   * 
   * @return A set of unique match name ids.
   */
  public synchronized Set<String> getMatchNames() {
    List<Match> matches = (List<Match>) getAllMatches();
    Set<String> names = new HashSet<String>();
    if (null == matches) {
      return null;
    }
    for (Match match : matches) {
      names.add(match.getName());
    }
    return names;
  }

  /**
   * Gets the matches for the match panel.
   * 
   * @return The list of matches.
   */
  public synchronized List<Match> getUserMatches() {
    List<Match> matches = new ArrayList<Match>();
    for (Match match : getAllMatches()) {
      matches.add(match);
    }
    return matches;
  }

  /**
   * Get user matches from the system.
   * 
   * @param email The email of user.
   * @return The list of matches of user.
   */
  public synchronized List<Match> getInvolvedMatches(String email) {
    List<Match> matches = new ArrayList<Match>();
    for (Match match : getAllMatches()) {
      for (Team team : match.getTeams()) {
        if (team.getOwner().getEmail().equals(email)) {
          matches.add(match);
        }
      }
    }
    return matches;
  }
}
